
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef WIN32
#include <windows.h>
#endif
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include "shader.h"

#ifndef M_PI
#define M_PI 3.1416f
#endif

#define min(a, b) ((a)<(b)?(a):(b))
#define max(a, b) ((a)>(b)?(a):(b))
#define GRID 0
#define SPHERE 1
#define TORUS 2
#define IMMEDIATE_MODE 0
#define V_ARRAY_MODE 1
#define VBO_MODE 2
#define MIN_TESELLATION 8
#define MAX_TESELLATION 256
#define TORUS_FIRST_RADIUS 4
#define TORUS_SECOND_RADIUS 2
#define SPHERE_RADIUS 5
#define GRID_LENGTH 10
#define ANIMATION_SPEED 10
#define MIN_SHININESS 12.5
#define MAX_SHININESS 200

// VBO Specific
#define NUM_BUFFERS 1

typedef struct Vec3f {
    float x;
    float y;
    float z;
} Vec3f;

typedef struct Vec2f {
    float u;
    float v;
} Vec2f;

typedef struct Vertex {
    Vec3f coord, norm;
    Vec2f parametricCoord;
} Vertex;

typedef struct Mesh {
   Vec3f * coords;
   Vec3f * norms;
   Vec2f * tex;
   int * faces;
   int rows;
   int cols;
   int numFacesIndices;
} Mesh;

enum RenderOptions {
   OPT_ANIMATION,
   OPT_TEXTURE,
   OPT_NORMALS,
   OPT_SHADER_GEOMETRY,
   OPT_FIXED_LIGHTING,
   OPT_VERTEX_LIGHTING,
   OPT_PHONG_LIGHTING,
   OPT_BUMPMAPPING,
   OPTSIZE //NOTE: this must be the last enumerator.
};

/* VBO specific vaiables */
int win;
GLuint shader;
GLuint vbo_vertices;
GLuint vbo_indices;
GLuint vbo_normals;
long lastTime;


/* shading variables */
GLuint torusShader;
GLuint blinnPhongPP;
GLuint phongPP;
GLuint blinnPhongPV;
GLuint phongPV;
GLuint bumpPV; 
GLuint currentShader = 0;

char shaderInfoString[1024];
char renderInfoString[1024];

/* camera variables */
int lastMouseX = 0;
int lastMouseY = 0;
float camRotX = 0.0f;
float camRotY = 0.0f;
float zoom = 10.0;
float mouseSensitivity = 0.3;
int buttonZoom = 0;
int buttonRotate = 0;

/*globals*/
short tesellation;
short currentObject;
short currentRenderMode;
Mesh geometry;
bool options[OPTSIZE];
float animation_step;
int shininess;

/*function prototypes*/
void allocateMesh( Mesh* mesh );
void createTorus( Mesh * torus,int height,int width, int R,int r);
void createSphere( Mesh * shpere,int r); 
void createGrid(Mesh * grid, int height, int width, int length );
void generateQuadFaces( int* faces, int height, int width );
void freeMesh( Mesh * mesh );
void createGeometry( );
void init();
void drawScene();
void drawString(char *string, float x, float y);
void drawHud();
void drawAxes();
void drawNormals(Mesh *  mesh);
void drawObjectVBO( Mesh * mesh );
void drawObjectVA( Mesh * geometry );
void drawObjectImmediate( Mesh * mesh );
void updateRenderString();
void bindBufferData();
void unbindBufferData();

/*
    stride == sizeof( Vertex )
    offset coord == 0;
    offset normal ==

    Vertex tmp;
    stride = sizeof( tmp );
    offset_Vert = &tmp.coord - &tmp;
    offset_Norm = &tmp.norm - &tmp;
    offset_Coord = &tmp.texCoor - &tmp;
*/

void allocateMesh( Mesh* mesh )
{
   mesh->coords = malloc(sizeof(Vec3f) * (mesh->rows)* (mesh->cols));
   mesh->norms = malloc(sizeof(Vec3f) * (mesh->rows)* (mesh->cols));
   mesh->faces = (int*)malloc( sizeof(int*) * mesh->rows* mesh->cols * 4);
}


void createTorus( Mesh * torus,int height,int width, int R,int r)
{
   torus->rows = height;
   torus->cols = width;
   torus->numFacesIndices = (height-1) * (width-1) * 4;
   allocateMesh( torus );
      
   /*generating co-ords*/
   for( int i = 0; i < height; i++ )
   {
      for( int j = 0; j < width; j++ )
      {
         float u = 0, v = 0;               
         
         u = (2*M_PI)*(j/((float)(height-1))); 
         v = (2*M_PI)*(i/((float)(height-1)));

         torus->coords[i*width+j].x = (R + r * cos(u)) * cos(v);
         torus->coords[i*width+j].y = (R + r * cos(u)) * sin(v);
         torus->coords[i*width+j].z = r * sin(u);       
 
         torus->norms[i*width+j].x = cos(u) * cos(v);
         torus->norms[i*width+j].y = cos(u) * sin(v);
         torus->norms[i*width+j].z = sin(u);
      }
   } 
   
   /*generate faces for quadstrips*/
   generateQuadFaces( torus->faces, height, width );
}

void createGrid(Mesh * grid, int height, int width, int length )
{
   grid->rows = height;
   grid->cols = width;
   grid->numFacesIndices = (height-1) * (width-1) * 4;
   allocateMesh(grid);
   
   for( int i = 0; i < grid->rows; i++ )
   {
      float v = (float)i / (grid->rows-1);
      
      for( int j = 0; j < grid->cols; j++ )
      {
         float u = (float)j / (grid->cols-1);
         
         grid->coords[i*width+j].x = (-0.5*length)+(u*length);
         grid->coords[i*width+j].y = (-0.5*length)+(v*length);
         grid->coords[i*width+j].z = 0;       
 
         grid->norms[i*width+j].x = 0;
         grid->norms[i*width+j].y = 0;
         grid->norms[i*width+j].z = 1;
      }
   }
   
   /*generate faces for quadstrips*/
   generateQuadFaces( grid->faces, height, width );
}

/*


   change to accept tesellation from parameter!!!!!!!


*/

void createSphere(Mesh *sphere, int r)
{
   sphere->rows = tesellation;
   sphere->cols = tesellation;
   sphere->numFacesIndices = (tesellation-1) * (tesellation-1) * 4;
   allocateMesh(sphere);
   
   // Create the vertices for a sphere.
   for (int i = 0; i < tesellation; ++i) 
   {
      float v = M_PI * (float)i / (sphere->rows-1);
      for (int j = 0; j < tesellation; ++j)
      {
         float u = 2.0 * M_PI * (float)j / (sphere->cols-1);
         
         sphere->coords[i*tesellation+j].x = 
         sphere->norms[i*tesellation+j].x  = r * cos(u) * sin(v);
         
         sphere->coords[i*tesellation+j].y = 
         sphere->norms[i*tesellation+j].y  = r * sin(u) * sin(v);
         
         sphere->coords[i*tesellation+j].z = 
         sphere->norms[i*tesellation+j].z  = r * cos(v);
      }
   }
   
   /*generate faces for quadstrips*/
   generateQuadFaces( sphere->faces, tesellation, tesellation );
   
}

void generateQuadFaces( int* faces, int height, int width )
{
   int i1, i2, i3, i4;
   int next = 0;
   
   /*generating faces*/
   for( int i = 0; i < height-1; i++ )
   {
      for( int j = 0; j < width-1; j++ )
      {
         i1 = i * width + j;
         i2 = i * width + (j+1);
         i3 = (i+1) * width + j;
         i4 = (i+1) * width + (j+1);
         faces[next++] = i1;
         faces[next++] = i2;
         faces[next++] = i3;
         faces[next++] = i4;
      }      
   }
}

void freeMesh( Mesh * mesh )
{
   if( mesh->norms )
      free( mesh->norms );
    
   if( mesh->coords )
      free( mesh->coords );       

   if( mesh->faces )
      free( mesh->faces);

   mesh->coords = NULL;
   mesh->norms = NULL;
   mesh->faces = NULL;
   mesh->rows = 0;
   mesh->cols = 0;
   mesh->numFacesIndices = 0;
}

void createGeometry()
{
   if( geometry.coords || geometry.faces || geometry.norms )
      freeMesh( &geometry );
      
   if( currentObject == GRID )
      createGrid( &geometry,tesellation,tesellation, GRID_LENGTH);
   else if( currentObject == SPHERE )
      createSphere(&geometry,SPHERE_RADIUS);
   else
      createTorus( &geometry, tesellation,tesellation,
                   TORUS_FIRST_RADIUS,TORUS_SECOND_RADIUS);
}

void init()
{
   options[ OPT_FIXED_LIGHTING ] = true;
   options[ OPT_VERTEX_LIGHTING ] = true; 
   
   animation_step = 0;
   shininess = 50.0;
   
   currentRenderMode = IMMEDIATE_MODE;
   tesellation = MIN_TESELLATION;
   currentObject = TORUS;
   geometry.rows = MIN_TESELLATION;
   geometry.cols = MIN_TESELLATION;
   createGeometry();
   updateRenderString();
}

void drawScene()
{
   /*draw normals*/
   if( options[OPT_NORMALS])
      drawNormals( &geometry );

   /* activate shader (or no shader) */
   glUseProgram(currentShader);
   
   /*draw geometry by current mode*/
   if( currentRenderMode == IMMEDIATE_MODE )
      drawObjectImmediate( &geometry );
   else if( currentRenderMode == V_ARRAY_MODE )
      drawObjectVA( &geometry );
   else
      drawObjectVBO( &geometry );
}

// Generate the VBOs
void genBuffers() {

   // Generate the buffers
   glGenBuffers(1, &vbo_vertices);
   glGenBuffers(1, &vbo_indices );
   glGenBuffers(1, &vbo_normals );

   bindBufferData();
   unbindBufferData();
}

void bindBufferData() {
 
   glBindBuffer(GL_ARRAY_BUFFER, vbo_vertices); 
   glBufferData(GL_ARRAY_BUFFER, sizeof(Vec3f)*tesellation*tesellation, geometry.coords,GL_DYNAMIC_DRAW);

   glBindBuffer(GL_ARRAY_BUFFER, vbo_normals );   
   glBufferData(GL_ARRAY_BUFFER, sizeof(Vec3f)*tesellation*tesellation, geometry.norms,GL_DYNAMIC_DRAW);

   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_indices);
   glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Vec3f)*tesellation*tesellation, geometry.faces,GL_DYNAMIC_DRAW);
}

void unbindBufferData() {

   glBindBuffer(GL_ARRAY_BUFFER, 0);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void drawObjectVBO( Mesh * mesh )
{
   // bindBufferData();
   glEnableClientState(GL_VERTEX_ARRAY);
   glBindBuffer(GL_ARRAY_BUFFER, vbo_vertices);
   glBufferData(GL_ARRAY_BUFFER, sizeof(Vec3f)*tesellation*tesellation, geometry.coords,GL_DYNAMIC_DRAW);

   glEnableClientState(GL_NORMAL_ARRAY); 
   glBindBuffer(GL_ARRAY_BUFFER, vbo_normals );   
   glBufferData(GL_ARRAY_BUFFER, sizeof(Vec3f)*tesellation*tesellation, geometry.norms,GL_DYNAMIC_DRAW);
    
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_indices);
   glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Vec3f)*tesellation*tesellation, geometry.faces,GL_DYNAMIC_DRAW);

   glVertexPointer( 3,GL_FLOAT,0,0 );   
   glNormalPointer(GL_FLOAT,0, 0); 
   glDrawElements(GL_QUAD_STRIP, tesellation*tesellation,GL_UNSIGNED_INT, 0 );

   unbindBufferData();
   //glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_NORMAL_ARRAY);
}

void drawObjectVA( Mesh * mesh )
{
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_NORMAL_ARRAY);
   glVertexPointer(3, GL_FLOAT, sizeof(Vec3f), mesh->coords);
   glNormalPointer(GL_FLOAT, sizeof(Vec3f), mesh->norms );
   glDrawElements(GL_QUAD_STRIP, mesh->numFacesIndices, GL_UNSIGNED_INT, mesh->faces); 
   glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_NORMAL_ARRAY);
}

void drawObjectImmediate( Mesh * mesh )
{
   int i,j;
   float x,y,z,nx,ny,nz;
   
   glBegin(GL_QUAD_STRIP);
   for( i = 0; i < mesh->numFacesIndices; i+=4 )
   {
      for(j = 0; j < 4; j++ )
      {
         x = mesh->coords[ mesh->faces[i+j] ].x;
         y = mesh->coords[ mesh->faces[i+j] ].y;
         z = mesh->coords[ mesh->faces[i+j] ].z;
         
         nx = mesh->norms[ mesh->faces[i+j] ].x;
         ny = mesh->norms[ mesh->faces[i+j] ].y;
         nz = mesh->norms[ mesh->faces[i+j] ].z;
         
         glNormal3f(nx,ny,nz);
         glVertex3f(x,y,z); 
      }
   }
   glEnd();
}

void drawString(char *string, float x, float y)
{
   char *c;
   glRasterPos2f(x, y);
   for (c=string; *c != '\0'; c++)
      glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10 , *c);
}

void drawHud()
{
   glColor3f(1.0, 0.5, 0.0);
   drawString(shaderInfoString, 0.0, 0.04);
   drawString( renderInfoString, 0.0, 0.01);
}

void drawAxes()
{
   glPushMatrix();
   glUseProgram(0);
   glDisable(GL_LIGHTING);
   glBegin(GL_LINES);
   glColor3f(1, 0, 0);
   glVertex3f(0, 0, 0); glVertex3f(1, 0, 0);
   glColor3f(0, 1, 0);
   glVertex3f(0, 0, 0); glVertex3f(0, 1, 0);
   glColor3f(0, 0, 1);
   glVertex3f(0, 0, 0); glVertex3f(0, 0, 1);
   glEnd();
   glPopMatrix();
   glEnable(GL_LIGHTING);
}

void drawNormals( Mesh * mesh )
{
   glBegin(GL_LINES);
   for (int i = 0; i < mesh->rows*mesh->cols; i++)
   {
      Vec3f coord = mesh->coords[i];
      Vec3f norm = mesh->norms[i];
      glVertex3f( coord.x, coord.y, coord.z );
      glVertex3f( coord.x+ norm.x, coord.y + norm.y,coord.z+norm.z);
   }
   glEnd();
}

void display()
{
   static const float lightPos[] = {2.0, 2.0, 2.0, 0.0};
   static const float ambient[] = {0.5, 0.5, 0.5, 1.0};
   static const float diffuse[] = {1.0, 0.0, 0.0, 1.0};
   static const float specular[] = {1.0, 1.0, 1.0, 1.0};
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glLoadIdentity();
   
   /* camera transform */
   glTranslatef(0.0, 0.0, -zoom); /* zoom transform */
   glRotatef(camRotX, 1.0, 0.0, 0.0); /* rotation around x axis */
   glRotatef(camRotY, 0.0, 1.0, 0.0); /* rotation around y axis */
   
   /* set the light position */
   glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
   glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
   glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
   glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
   glMaterialf(GL_FRONT, GL_SHININESS, shininess);
   
   /*draw axes*/
   drawAxes();
   
   /*animation transform*/
   glRotatef( animation_step, 0,1,0);
   
   /* draw the scene */
   drawScene();

   /*Render the hud*/ 
   glDisable(GL_DEPTH_TEST);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
   glOrtho(0,1,0,1,-1,1); /* orthographic hud */
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   
   glUseProgram(0);
   glDisable(GL_LIGHTING);
   glTranslatef(0.0, 0.0, -0.5);
   drawHud();
   glEnable(GL_LIGHTING);
   
   glMatrixMode(GL_PROJECTION);
   glPopMatrix(); /* back to previous projection mode */
   glMatrixMode(GL_MODELVIEW);
   glEnable(GL_DEPTH_TEST);
   
   glutSwapBuffers();
}

void idle()
{
   GLint loc;
   long thisTime;
   float deltaTime;
   
   /* calculate frame time */
   thisTime = glutGet(GLUT_ELAPSED_TIME);
   deltaTime = (thisTime - lastTime) * 0.001;
   lastTime = thisTime;

   /* pass in the current time to the shader */
   if (shader != 0 && (loc = glGetUniformLocation(shader, "time")) != -1)
      glUniform1f(loc, thisTime * 0.001f);
   
   
   /*calculate animation step*/
   if( options[OPT_ANIMATION] )
   {
      animation_step += ANIMATION_SPEED * deltaTime;
      
      if( animation_step > 360 )
         animation_step -= 360;
   }
   /* notify glut that the scene needs to be redrawn */
   glutPostRedisplay();
}

void reshape(int x, int y)
{
   float aspect;
   if (y == 0) y = 1;
   aspect = x/(float)y;
   /* set area on screen to draw to */
   glViewport(0, 0, x, y);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   /* set perspective projection */
   gluPerspective(75.0, aspect, 0.1, 100);
   glMatrixMode(GL_MODELVIEW); /* don't forget to go back to modelview */
}

void updateRenderString()
{
    if( currentRenderMode == IMMEDIATE_MODE )
        strcpy( renderInfoString, "Immediate Render Mode" );
    else if( currentRenderMode == V_ARRAY_MODE )
        strcpy( renderInfoString, "Vertex Array Mode" );
    else
        strcpy( renderInfoString, "Vertex Buffer Object Mode" );
}

void updateShadingMethod()
{
   
   //fixed pipeline
   if (options[OPT_FIXED_LIGHTING])
   {
      currentShader = 0; 
      strcpy(shaderInfoString, "Fixed pipeline");
   }
   
   //phong model lighting
   else if( options[ OPT_PHONG_LIGHTING ] )
   {
      if( options[OPT_VERTEX_LIGHTING] )
      {
         currentShader = phongPV; 
         strcpy(shaderInfoString, "Phong, per vertex");
      }
      else
      {
         currentShader = 0; 
         strcpy(shaderInfoString, "Phong, per pixel (not implemented, still fixed pipeline)");
      }
   }
   
   //blinn phong model lighting
   else if( options[ OPT_VERTEX_LIGHTING ] )
   {
      if( options[OPT_VERTEX_LIGHTING] )
      {
         currentShader = blinnPhongPV; 
         strcpy(shaderInfoString, "Blinn Phong, per vertex");
      }
      else
      {
         currentShader = 0; 
         strcpy(shaderInfoString, "Blinn Phong, per pixel (not implemented, still fixed pipeline)");
      }
   }

   // Bump mapping
   else
   {
      if( options[ OPT_BUMPMAPPING ] )
      {
         currentShader = bumpPV;
         strcpy(shaderInfoString, "Bump Mapping, per vertex");
      }
      else
      {
         currentShader = 0;
         strcpy(shaderInfoString, "Bump Mapping, per fragment (NOT YET IMPLEMENTED)");
      }
   }

}
void cleanup()
{
   freeMesh( &geometry );
}

void keyDown(unsigned char key, int x, int y)
{
   switch(key)
   {
   case 27:
   case 'q':
   case'Q':
      cleanup();
      glutDestroyWindow(win);
      exit(0);
   case 'T':
   case 't':
      if (key == 'T')
         tesellation = min(MAX_TESELLATION,tesellation * 2 );
      else
         tesellation = max(MIN_TESELLATION, tesellation / 2);
      createGeometry();
      break;
   case 'o':
   case 'O':
      if(++currentObject > TORUS)
         currentObject = GRID;
      createGeometry();
      break;
   case 'a':
   case 'A':
      options[OPT_ANIMATION] = !options[OPT_ANIMATION];
      break;
   case 'n':
   case 'N':
      options[OPT_NORMALS] = !options[OPT_NORMALS];
      break;
   case 'g':
   case 'G':
      options[OPT_SHADER_GEOMETRY] = !options[OPT_SHADER_GEOMETRY];
      break;
   case 'e':
   case 'E':
      if(++currentRenderMode > VBO_MODE )
         currentRenderMode = IMMEDIATE_MODE;
      updateRenderString();
      break;
   case 's':
   case 'S':
      options[OPT_FIXED_LIGHTING] = !options[OPT_FIXED_LIGHTING];
      updateShadingMethod();
      break;
   case 'p':
   case 'P':
      options[OPT_VERTEX_LIGHTING] = !options[OPT_VERTEX_LIGHTING];
      updateShadingMethod();
      break;
   case 'm':
   case 'M':
      options[OPT_PHONG_LIGHTING] = !options[OPT_PHONG_LIGHTING];
      updateShadingMethod();
      break;
   case 'b':
   case 'B':
      options[OPT_BUMPMAPPING] = !options[OPT_BUMPMAPPING];
      updateShadingMethod();
      break;
   case 'h':
      shininess = max( shininess/2, MIN_SHININESS );
      break;
   case 'H':
      shininess = min( shininess*2, MAX_SHININESS );
      break;
   default:
      break;
   }
}

void mouseDown(int button, int state, int x, int y)
{
   if (button == GLUT_LEFT_BUTTON)
      buttonRotate = state == GLUT_DOWN;
   if (button == GLUT_RIGHT_BUTTON)
      buttonZoom = state == GLUT_DOWN;
}

void mouseMove(int x, int y)
{
   /* get change in mouse position */
   int dx = x - lastMouseX;
   int dy = y - lastMouseY;
   if (buttonRotate)
   {
      /* rotate camera based on mouse movement */
      camRotX += dy * mouseSensitivity;
      camRotY += dx * mouseSensitivity;
      if (camRotY > 180.0) camRotY -= 360.0;
      if (camRotY <= -180.0) camRotY += 360.0;
      if (camRotX > 90.0) camRotX = 90.0;
      if (camRotX < -90.0) camRotX = -90.0;
   }
   if (buttonZoom)
   {
      /* zoom in and out based on y mouse movement */
      zoom += dy * zoom * mouseSensitivity * 0.1f;
      if (zoom < 0.1) zoom = 0.1;
      if (zoom > 100.0) zoom = 100.0;
   }

   /* update last mouse position */
   lastMouseX = x;
   lastMouseY = y;
}

int main(int argc, char** argv)
{   
   /* initialize glut and create a window */
   glutInit( &argc, argv );
   glutInitDisplayMode( GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
   glutInitWindowSize(500, 500);
   win = glutCreateWindow("test");
     
   glewInit();

   /*initialise state*/
   init();

   /*generating buffers*/
   genBuffers();

   /* set up the glut callbacks */
   glutIdleFunc(idle);
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyDown);
   glutMouseFunc(mouseDown);
   glutMotionFunc(mouseMove);
   glutPassiveMotionFunc(mouseMove);
   glDepthFunc(GL_LESS);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_NORMALIZE);

   /* create our shaders */
   //blinnPhongPP = loadShader("blinnPhongPP.vert", NULL);
   //phongPP= loadShader("phongPP.vert", NULL);
   blinnPhongPV= loadShader("blinnPhongPV.vert", NULL);
   phongPV = loadShader("phongPV.vert", NULL); 
   bumpPV = loadShader("bumpPV.vert", NULL); 
   updateShadingMethod();

   /* initialize lastTime and enter main loop */
   lastTime = glutGet(GLUT_ELAPSED_TIME);
   glutMainLoop();

   return 0;
}
